/* 
 * AUTHOR: Kevin Lam
 */

package com.apps.datastore;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.TimeZone;
import java.util.logging.Logger;

import com.apps.datastore.dao.CachedCourseInformationObject;
import com.apps.datastore.dao.ContactInformationObject;
import com.apps.datastore.dao.ContactInformationObject.CARRIER;
import com.apps.datastore.dao.LoginInformationObject;
import com.apps.datastore.dao.UniqueCourseObject;
import com.apps.outgoing.EmailNotifier;
import com.apps.outgoing.SMSNotifier;
import com.apps.services.UBCSectionDetailService;
import com.apps.utils.BCryptUtils;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.PreparedQuery;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Query.FilterOperator;
import com.google.appengine.api.datastore.Transaction;

public class NotifierDatastore {

	private static final Logger log = Logger.getLogger(NotifierDatastore.class
			.getName());
	private DatastoreService datastore;

	public NotifierDatastore() {
		datastore = DatastoreServiceFactory.getDatastoreService();
	}


	public void dropSchema() {
		Query q = new Query();
		q.setKeysOnly();
		List<Entity> l = datastore.prepare(q).asList(
				FetchOptions.Builder.withDefaults());
		for (Iterator i = l.iterator(); i.hasNext();) {
			Entity e = (Entity) i.next();
			log.info("deleting " + e.getKey());
			System.out.println("deleting " + e.getKey());
			datastore.delete(e.getKey());
		}
	}


	public void initSchema() {
		Entity e = new Entity("Unique Course Set", "COURSE_ID_SET");
		datastore.put(e);
		e = new Entity("Cache List", "CACHE_LIST");
		datastore.put(e);
		e = new Entity("Contact Information Set", "CONTACT_SET");
		datastore.put(e);
		e = new Entity("Login Information Set", "LOGIN_SET");
		datastore.put(e);
		e = new Entity("Account Information Set", "ACCOUNT_SET");
		datastore.put(e);
	}

	public List<UniqueCourseObject> getCacheUniqueCourseObjectList()
			throws EntityNotFoundException {
		List<Entity> cel = getCacheEntityList();
		List<UniqueCourseObject> cl = new ArrayList<UniqueCourseObject>();
		for (Iterator i = cel.iterator(); i.hasNext();) {
			Entity e = (Entity) i.next();
			cl.add(getUniqueCourse((Key) e.getProperty("courseKey")));
		}
		return cl;
	}

	public List<CachedCourseInformationObject> getCachedCourseInformationObjectList()
			throws EntityNotFoundException {
		List<Entity> cel = getCacheEntityList();
		List<CachedCourseInformationObject> cciol = new ArrayList<CachedCourseInformationObject>();
		for (Iterator i = cel.iterator(); i.hasNext();) {
			Entity e = (Entity) i.next();
			cciol.add(getCourseInformation(e.getKey()));
		}
		return cciol;
	}

	public List<ContactInformationObject> getContactEntryList(
			UniqueCourseObject uco) throws EntityNotFoundException {
		Query q = new Query("Cache");
		q.addFilter("courseKey", Query.FilterOperator.EQUAL,
				getUniqueCourseEntity(uco).getKey());
		Entity qe = datastore.prepare(q).asSingleEntity();
		List<Entity> ceel = getContactEntryEntityList(qe.getKey());
		List<ContactInformationObject> ciol = new ArrayList<ContactInformationObject>();
		for (Iterator i = ceel.iterator(); i.hasNext();) {
			Entity e = (Entity) i.next();
			ciol.add(getContactInformation((Key) e.getProperty("contactKey")));
		}
		return ciol;
	}
	

	public boolean addCourse(UniqueCourseObject uco)
			throws EntityNotFoundException {
		boolean b = checkCourseExist(uco);
		if (!b) {
			Entity course = createUniqueCourseEntity(uco);
			createCacheEntity(course.getKey());
		}
		return !b;
	}
	
	public LoginInformationObject getLoginInformationObject(String username) {
		Query q = new Query("Login Information");
		q.addFilter("username", Query.FilterOperator.EQUAL, username);
		Entity qe = datastore.prepare(q).asSingleEntity();
		String password = (String) qe.getProperty("password");
		boolean active = (Boolean) qe.getProperty("active");
		LoginInformationObject lio = new LoginInformationObject(username,password,active);
		return lio;
	}
	
	public boolean validateLogin(String username) {
		Query q = new Query("Login Information");
		q.addFilter("username", Query.FilterOperator.EQUAL, username);
		Entity qe = datastore.prepare(q).asSingleEntity();
		boolean b = !(Boolean) qe.getProperty("active");
		if (b)
			qe.setProperty("active", true);
		datastore.put(qe);
		return b;
	}
	
	private void createLoginInformationEntity(LoginInformationObject lio){
		Transaction txn = datastore.beginTransaction();
		Key k = KeyFactory.createKey("Login Information Set", "LOGIN_SET");
		Entity e = new Entity("Login Information", k);
		e.setProperty("username", lio.getUsername());
		e.setProperty("password", lio.getPassword());
		datastore.put(e);
		txn.commit();
		
	}
	
	public boolean addLogin(LoginInformationObject lio){
		boolean b = checkLoginExist(lio);
		if(!b)
			createLoginInformationEntity(lio);
		return !b;
	}
	
	public boolean checkLoginExist(LoginInformationObject lio){
		Query q = new Query("Login Information");
		q.addFilter("username", Query.FilterOperator.EQUAL,
				lio.getUsername());
		PreparedQuery pq = datastore.prepare(q);
		if (pq.countEntities(FetchOptions.Builder.withDefaults()) == 0)
			return false;
		return true;
	}

	public boolean addNotifier(UniqueCourseObject uco,
			ContactInformationObject cio) throws EntityNotFoundException {
		boolean b = checkNotifierExist(uco, cio);
		if (!b){
			Entity contactInfo = createContactInformationEntity(cio);
			createContactEntryEntity(uco, contactInfo.getKey());
		}
		return !b;
	}

	private UniqueCourseObject getUniqueCourse(Key courseKey)
			throws EntityNotFoundException {
		Entity e = datastore.get(courseKey);
		UniqueCourseObject uco = new UniqueCourseObject(
				(String) e.getProperty("departmentName"),
				(String) e.getProperty("courseNumber"),
				(String) e.getProperty("sectionNumber"));
		return uco;
	}

	private CachedCourseInformationObject getCourseInformation(Key cacheKey)
			throws EntityNotFoundException {
		Entity e = datastore.get(cacheKey);
		CachedCourseInformationObject ccio = new CachedCourseInformationObject(
				(Long) e.getProperty("generalSeats"),
				(Long) e.getProperty("restrictedSeats"));
		return ccio;
	}

	private List<Entity> getContactInformationEntityList() {
		Query q = new Query("Contact Information");
		Key k = KeyFactory.createKey("Contact Information Set", "CONTACT_SET");
		q.setAncestor(k);
		List<Entity> results = datastore.prepare(q).asList(
				FetchOptions.Builder.withDefaults());
		return results;
	}

	private ContactInformationObject getContactInformation(Key contactKey)
			throws EntityNotFoundException {
		Entity e = datastore.get(contactKey);
		ContactInformationObject cio = new ContactInformationObject(
				(String) e.getProperty("emailAddress"),
				(String) e.getProperty("phoneNumber"),
				(Long) e.getProperty("seatConfig"),
				(Long) e.getProperty("notifyConfig"),
				(String) e.getProperty("carrier"));
		return cio;
	
	}

	private Entity createCacheEntity(Key courseKey)
			throws EntityNotFoundException {
		Key k = KeyFactory.createKey("Cache List", "CACHE_LIST");
		UBCSectionDetailService w = new UBCSectionDetailService();
		w.initContent(getUniqueCourse(courseKey));
		Entity e = new Entity("Cache", k);
		e.setProperty("courseKey", courseKey);
		e.setProperty("generalSeats", w.getGenSeatsRemain());
		e.setProperty("restrictedSeats", w.getRestrictSeatsRemain());
		datastore.put(e);
		return e;
	}

	private void destroyCacheEntity(Key cacheKey) {
		Query q = new Query();
		q.setAncestor(cacheKey);
		q.setKeysOnly();
		List<Entity> results = datastore.prepare(q).asList(
				FetchOptions.Builder.withDefaults());
		for (Iterator i = results.iterator(); i.hasNext();) {
			Transaction txn = datastore.beginTransaction();
			Entity e = (Entity) i.next();
			datastore.delete(e.getKey());
			txn.commit();
		}
	}

	private List<Entity> getCacheEntityList() {
		Query q = new Query("Cache");
		Key k = KeyFactory.createKey("Cache List", "CACHE_LIST");
		q.setAncestor(k);
		List<Entity> results = datastore.prepare(q).asList(
				FetchOptions.Builder.withDefaults());
		return results;
	}

	public void updateCache() throws EntityNotFoundException {
		List<Entity> cel = getCacheEntityList();
		for (Iterator i = cel.iterator(); i.hasNext();) {
			Entity e = (Entity) i.next();
			UBCSectionDetailService w = new UBCSectionDetailService();
			w.initContent(getUniqueCourse((Key) e.getProperty("courseKey")));
			e.setProperty("generalSeats", w.getGenSeatsRemain());
			e.setProperty("restrictedSeats", w.getRestrictSeatsRemain());
			datastore.put(e);
			if (getContactEntryEntityList(e.getKey()).isEmpty()) {
				datastore.delete(e.getKey());
				datastore.delete((Key) e.getProperty("courseKey"));
			}
		}
		log.info("Cache successfully updated");
	}

	private static final String EMAIL_NOTIFICATION = "Course Information Update: <DEPARTMENT> <COURSE> <SECTION> has <NUMBER_OF_SEATS> <SEAT_TYPE> seat(s) remaining at <TIMESTAMP>. Please visit this link to register(requires CWL authentication): https://courses.students.ubc.ca/cs/main?pname=subjarea&tname=subjareas&req=5&dept=<DEPARTMENT>&course=<COURSE>&section=<SECTION>";
	private static final String SMS_NOTIFICATION = "Course Information Update: <DEPARTMENT> <COURSE> <SECTION> has <NUMBER_OF_SEATS> <SEAT_TYPE> seat(s) remaining at <TIMESTAMP>. Please visit UBC SSC to register.";

	private String prepareMessage(String template, String dept, String course,
			String section, long numberOfSeats, String seatType) {
		String s = template;
		s = s.replaceAll("<DEPARTMENT>", dept);
		s = s.replaceAll("<COURSE>", course);
		s = s.replaceAll("<SECTION>", section);
		s = s.replaceAll("<NUMBER_OF_SEATS>", "" + numberOfSeats);
		s = s.replaceAll("<SEAT_TYPE>", seatType);
		Date d = Calendar.getInstance(TimeZone.getTimeZone("PST")).getTime();
		s = s.replaceAll("<TIMESTAMP>", d.toString());
		return s;

	}

	public void notifyAvailableCourses() throws EntityNotFoundException {
		List<UniqueCourseObject> cucol = getCacheUniqueCourseObjectList();
		for (Iterator i = cucol.iterator(); i.hasNext();) {
			UniqueCourseObject uco = (UniqueCourseObject) i.next();
			Entity e = getCacheEntity(uco);
			long gSeats = (Long) e.getProperty("generalSeats");
			long rSeats = (Long) e.getProperty("restrictedSeats");
			List<Entity> ceel = getContactEntryEntityList(e.getKey());
			for (Iterator j = ceel.iterator(); j.hasNext();) {
				Entity ce = (Entity)j.next();
				ContactInformationObject cio = getContactInformation((Key)ce.getProperty("contactKey"));
				if (cio.getSeatConfig() == 1 && gSeats > 0) {
					// notify gen seats avail
					if (cio.getNotifyConfig() == 1
							&& cio.getEmailAddress() != null
							&& cio.getEmailAddress() != "") {
						// email notify
						EmailNotifier en = new EmailNotifier();
						String msg = prepareMessage(EMAIL_NOTIFICATION,
								uco.getDepartmentName(), uco.getCourseNumber(),
								uco.getSectionNumber(), gSeats, "general");
						EmailNotifier.sendMessage(cio.getEmailAddress(), msg);
						destroyContactEntry(ce.getKey());
					}
					if (cio.getNotifyConfig() == 2
							&& cio.getPhoneNumber() != null
							&& cio.getPhoneNumber() != ""
							&& cio.getCarrier() != CARRIER.NULL) {
						// sms notify
						SMSNotifier smsn = new SMSNotifier();
						String msg = prepareMessage(SMS_NOTIFICATION,
								uco.getDepartmentName(), uco.getCourseNumber(),
								uco.getSectionNumber(), gSeats, "general");
						SMSNotifier.sendMessage(cio.getPhoneNumber(),
								cio.getCarrier(), msg);
						destroyContactEntry(ce.getKey());
					}
				}
				if (cio.getSeatConfig() == 2 && rSeats > 0) {
					// notify res seats avail
					if (cio.getNotifyConfig() == 1
							&& cio.getEmailAddress() != null
							&& cio.getEmailAddress() != "") {
						// email notify
						EmailNotifier en = new EmailNotifier();
						String msg = prepareMessage(EMAIL_NOTIFICATION,
								uco.getDepartmentName(), uco.getCourseNumber(),
								uco.getSectionNumber(), rSeats, "restricted");
						EmailNotifier.sendMessage(cio.getEmailAddress(), msg);
						destroyContactEntry(ce.getKey());
					}
					if (cio.getNotifyConfig() == 2
							&& cio.getPhoneNumber() != null
							&& cio.getPhoneNumber() != ""
							&& cio.getCarrier() != CARRIER.NULL) {
						// sms notify
						SMSNotifier smsn = new SMSNotifier();
						String msg = prepareMessage(SMS_NOTIFICATION,
								uco.getDepartmentName(), uco.getCourseNumber(),
								uco.getSectionNumber(), rSeats, "restricted");
						SMSNotifier.sendMessage(cio.getPhoneNumber(),
								cio.getCarrier(), msg);
						destroyContactEntry(ce.getKey());
					}
				}

			}
		}
	}

	private Entity createContactEntryEntity(UniqueCourseObject uco,
			Key contactKey) {
		Entity e = new Entity("Contact Entry", getCacheEntity(uco).getKey());
		e.setProperty("contactKey", contactKey);
		Transaction txn = datastore.beginTransaction();
		datastore.put(e);
		txn.commit();
		return e;
	}

	private List<Entity> getContactEntryEntityList(Key cacheKey) {
		Query q = new Query("Contact Entry");
		q.setAncestor(cacheKey);
		List<Entity> results = datastore.prepare(q).asList(
				FetchOptions.Builder.withDefaults());
		return results;
	}

	private void destroyContactEntry(Key contactEntryKey) throws EntityNotFoundException {
		Entity e = datastore.get(contactEntryKey);
		Key k = (Key)e.getProperty("contactKey");
		datastore.delete(k);
		datastore.delete(contactEntryKey);
	}

	private Entity getContactInformationEntity(ContactInformationObject cio) {
		Query q = new Query("Contact Information");
		q.addFilter("emailAddress", FilterOperator.EQUAL, cio.getEmailAddress());
		q.addFilter("phoneNumber", FilterOperator.EQUAL, cio.getPhoneNumber());
		q.addFilter("seatConfig", FilterOperator.EQUAL, cio.getSeatConfig());
		q.addFilter("notifyConfig", FilterOperator.EQUAL, cio.getNotifyConfig());
		q.addFilter("carrier", FilterOperator.EQUAL, cio.getCarrier()
				.getGateway());
		return datastore.prepare(q).asSingleEntity();
	}

	private Entity createContactInformationEntity(ContactInformationObject cio) {
		Transaction txn = datastore.beginTransaction();
		Key k = KeyFactory.createKey("Contact Information Set", "CONTACT_SET");
		Entity e = new Entity("Contact Information", k);
		e.setProperty("emailAddress", cio.getEmailAddress());
		e.setProperty("phoneNumber", cio.getPhoneNumber());
		e.setProperty("seatConfig", cio.getSeatConfig());
		e.setProperty("notifyConfig", cio.getNotifyConfig());
		e.setProperty("carrier", cio.getCarrier().getGateway());
		datastore.put(e);
		txn.commit();
		return e;
	}

	private void destroyContactInformationEntity(Key contactKey) {
		Transaction txn = datastore.beginTransaction();
		datastore.delete(contactKey);
		txn.commit();
	}

	private Entity createUniqueCourseEntity(UniqueCourseObject uco) {

		Transaction txn = datastore.beginTransaction();
		Key k = KeyFactory.createKey("Unique Course Set", "COURSE_ID_SET");
		Entity e = new Entity("Unique Course", k);
		e.setProperty("departmentName", uco.getDepartmentName());
		e.setProperty("courseNumber", uco.getCourseNumber());
		e.setProperty("sectionNumber", uco.getSectionNumber());
		datastore.put(e);
		txn.commit();
		return e;
	}

	private void destroyUniqueCourseEntity(Key courseKey) {
		Transaction txn = datastore.beginTransaction();
		datastore.delete(courseKey);
		txn.commit();
	}

	private Entity getCacheEntity(UniqueCourseObject uco) {
		Query q = new Query("Cache");
		q.addFilter("courseKey", Query.FilterOperator.EQUAL,
				getUniqueCourseEntity(uco).getKey());
		return datastore.prepare(q).asSingleEntity();
	}

	private Entity getUniqueCourseEntity(UniqueCourseObject uco) {
		Query q = new Query("Unique Course");
		q.addFilter("departmentName", Query.FilterOperator.EQUAL,
				uco.getDepartmentName());
		q.addFilter("courseNumber", Query.FilterOperator.EQUAL,
				uco.getCourseNumber());
		q.addFilter("sectionNumber", Query.FilterOperator.EQUAL,
				uco.getSectionNumber());
		PreparedQuery pq = datastore.prepare(q);
		return pq.asSingleEntity();
	}

	private boolean checkCourseExist(UniqueCourseObject uco) {
		Query q = new Query("Unique Course");
		q.addFilter("departmentName", Query.FilterOperator.EQUAL,
				uco.getDepartmentName());
		q.addFilter("courseNumber", Query.FilterOperator.EQUAL,
				uco.getCourseNumber());
		q.addFilter("sectionNumber", Query.FilterOperator.EQUAL,
				uco.getSectionNumber());
		PreparedQuery pq = datastore.prepare(q);
		if (pq.countEntities(FetchOptions.Builder.withDefaults()) == 0)
			return false;
		return true;
	}

	private boolean checkNotifierExist(UniqueCourseObject uco,
			ContactInformationObject cio) throws EntityNotFoundException {
		List<ContactInformationObject> ciol = getContactEntryList(uco);
		for (Iterator i = ciol.iterator(); i.hasNext();) {
			ContactInformationObject cio2 = (ContactInformationObject) i.next();
			if (cio2.equals(cio))
				return true;
		}
		return false;
	}
	
	
}
